package io.gitee.minelx.commontools.factor;

import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import static java.util.stream.Collectors.toList;

public class RegisterIterator<E> implements CloseableIterator<List<E>> {

    private final List<Factor<E>> factors;

    private final List<E> cell;

    private final List<CloseableIterator<E>> iterators;

    private boolean firstTrying;

    private RegisterIterator(List<Factor<E>> factors,
                             List<CloseableIterator<E>> iterators,
                             List<E> cell,
                             boolean firstTrying) {
        this.factors = factors;
        // step to end
        this.iterators = iterators;
        this.cell = cell;
        this.firstTrying = firstTrying;
    }

    @Override
    public boolean hasNext() {
        if (firstTrying) {
            return true;
        }
        return iterators.stream().anyMatch(Iterator::hasNext);
    }

    @Override
    public List<E> next() {
        if (firstTrying) {
            firstTrying = false; // never happens
            return packResultSet();
        }
        // move the last iterator forward
        moveForward(iterators.size() - 1);
        return packResultSet();
    }

    @Override
    public void close() {
        iterators.forEach(CloseableIterator::close);
    }

    private List<E> packResultSet() {
        // make a copy
        return new LinkedList<>(cell);
    }

    private void moveForward(int at) {
        Iterator<E> iterator = iterators.get(at);
        if (iterator.hasNext()) {
            E next = iterator.next();
            // bind next to result cell
            cell.set(at, next);
        } else {
            // move upper factor forward
            moveForward(at - 1);
            // clear factor at here
            CloseableIterator<E> newIterator = factors.get(at).iterator();
            E next = newIterator.next();
            cell.set(at, next);
            replaceFinishedIterator(at, newIterator);
        }
    }

    private void replaceFinishedIterator(int at, CloseableIterator<E> newIterator) {
        // close iterator which already finished
        iterators.get(at).close();
        iterators.set(at, newIterator);
    }

    public static <E> CloseableIterator<List<E>> create(List<Factor<E>> factors) {
        List<CloseableIterator<E>> iterators = factors.stream()
                .map(Factor::iterator)
                .collect(toList());
        boolean isAvailable = iterators.stream().allMatch(CloseableIterator::hasNext);
        if (!isAvailable) {
            return CloseableIterator.empty();
        }
        List<E> cell = iterators.stream().map(Iterator::next).collect(toList());
        return new RegisterIterator<>(factors, iterators, cell, true);
    }
}
